鐵人挑戰終於快到尾聲了…今天就來調整一些專案的小細節吧:
因為中文一個字是2個 byte,英文一個字母是1個 byte,但 .length 卻會將中文字當作1個字來回傳,這樣會造成我們在使用 .splice() 、.substring() 時計算長度錯誤,為了解決這種情況,我們可以另外寫一個函數來處理:
utils/formatter.js 中新增 subString 函數str(要判斷的字串)、n(要裁切的長度)str 長度是否大於 n ,若小於則回傳字串 str
len 長度+2,反之則 len +1(使用 RegExp.test() 來判斷字元是否為中文(符合表達式))len > n ,則跳出迴圈,反之則將當前字元加入暫時存放字串的 tmpStr
n 長度的字串 tmpStr 並加上 「…」export const subString = (str, n) => {
    if (str.replace(/[\u4e00-\u9fa5]/g, '**').length <= n) {
        return str;
    } else {
        let len = 0;
        let tmpStr = '';
        for (let i = 0; i < str.length; i++) {
            if (/[\u4e00-\u9fa5]/.test(str[i])) {
                len += 2;
            } else {
                len += 1;
            }
            if (len > n) {
                break;
            } else {
                tmpStr += str[i];
            }
        }
        return tmpStr + ' ...';
    }
};
接著一樣匯入使用即可!
<div className="">{loading ? <Skeleton count={3} /> : subString(el.content, 120)}</div>
目前的 Header 背景設定是透明的,我們可以定義一個狀態變數 isScrollDown 並搭配 window.addEventListener() 來監控使用者是否有往下瀏覽視窗,然後依據條件來渲染不同的樣式

const [isScrollDown, setIsScrollDown] = useState(false);
useEffect(() => {
    window.addEventListener('scroll', handleScroll, { passive: true });
    return () => {
        window.removeEventListener('scroll', handleScroll);
    };
}, []);
const handleScroll = () => {
    if (window.scrollY >= 80) {
        setIsScrollDown(true);
    } else {
        setIsScrollDown(false);
    }
};
接著在 className 用樣板字串`` 包起來,放入條件判斷來更改樣式
<div
    className={`sticky top-0 z-50 w-screen h-20 text-xl flex flex-row items-center px-14 mb-14 transition ease-in ${
        isScrollDown ? 'bg-yellow-50 h-16' : 'bg-transparent border-b border-b-yellow-700/50'
    }`}
>
...
</div>

建立 components/BackToTop.js 檔案,大致上跟 Header 相同概念,在 onClick 時呼叫 scrollToTop → 將視窗移至最上方 window.scrollTo(0, 0)
import React, { useState, useEffect } from 'react';
import { ArrowUpCircleIcon } from '@heroicons/react/24/solid';
export default function BackToTop() {
    const [isVisible, setIsVisible] = useState(false);
    useEffect(() => {
        window.addEventListener('scroll', handleScroll, { passive: true });
        return () => {
            window.removeEventListener('scroll', handleScroll);
        };
    }, []);
    const handleScroll = () => {
        if (window.scrollY > window.innerHeight / 5) {
            setIsVisible(true);
        } else {
            setIsVisible(false);
        }
    };
    const scrollToTop = () => {
        window.scrollTo(0, 0);
    };
    return (
        <div>
            {isVisible && (
                <div
                    className="fixed bottom-16 right-16 text-yellow-700/50 hover:cursor-pointer hover:text-yellow-700/80"
                    onClick={scrollToTop}
                >
                    <ArrowUpCircleIcon className="w-14 h-14" />
                </div>
            )}
        </div>
    );
}